#include <cstdlib>
#include "glut.h"
#include <time.h>
#include "v2d.h"
#include "templatevector.h"
#include "glutrenderingcontext.h"
#include "drawing.h"
#include <stdio.h>
#include <conio.h>
#include "edge.h"
#include "node.h"
#include "dijkstra.h"

int randColors();
/** the screen is 600x600 pixels (a square), 50x50 standard cartesian plane */
GLUTRenderingContext g_screen(V2DF(600, 600), V2DF(-3.0,-3.0), V2DF(25.0, 25.0));

#include "graphgen.h"
#include "traverse.h"
#include "solution.h"
#include <Windows.h>

TemplateHashMap<GraphNode *, DjikstraData> out;
//function to print out the dijkstra nodes with edges
void printDijkstra();
//CREATE: Generate and graph nodes!
TemplateVector<GraphNode> nodeList;
TemplateVector<GraphNode*> traversalList;
TemplateVector<GraphNode*> nodes;

bool animated = false;
bool print = false;
int input, listType = 0;

GraphNode * cursor;
const float rad = 0.3f;

int indexHighlight = 0;
//int colors[] = {
//	0x000000,
//	0x000055,
//	0x0000C0,
//	0x0000FF,
//	0x0000FF,
//	0x0000FF,
//	0x0000FF,
//	0x0000FF,
//	0x0000C0,
//	0x000055,
//	0x000000,
//};
int colors[] = {
	0x000000,
	0x000044,
	0x000088,
	0x0000CC,
	0x0000FF,
	0x0044CC,
	0x008888,
	0x00CC44,
	0x00FF00,
	0x44CC00,
	0x888800,
	0xCC4400,
	0xFF0000,
	0xFF0000,
	0xFF0000,
	0xCC4400,
	0x888800,
	0x44CC00,
	0x00FF00,
	0x00CC44,
	0x008888,
	0x0044CC,
	0x0000FF,
	0x0000CC,
	0x000088,
	0x000044,
	0x000000,
};

int numColors = sizeof(colors)/sizeof(colors[0]);
/**
* @param key what keyboard key was pressed (ASCII codes)
* @param x/y where the mouse was at the time
* @note: the following may be helpful:<code>
int state=glutGetModifiers();
if (state & GLUT_ACTIVE_SHIFT)	// shift is pressed</code>
*/
void setColor(int const & color)
{
	glColor3ubv((GLubyte*)&color);
}

const int BFS = 0, DFS = 1;
void keyboard(unsigned char key, int x, int y)
{
	float avgScreen;
	input = key;
	switch(key)
	{
	case 27:	input = false;	break;
	case ' ':	
		animated = false;
		print = !print;	
		break;
	case 127:
		animated = false;
		print = false;
		avgScreen = (g_screen.getMaxY()+g_screen.getMaxX())/2;
		randomGraph(nodeList, avgScreen*5, g_screen.getMaxX(), g_screen.getMaxY(), avgScreen/6);
		cursor = &nodeList.get(0);
		cursor->selected = true;
		cursor->markAdj();
		break;
	case 'D':
		listType = DFS;
		animated = false;
		print = false;
		indexHighlight = -numColors;
		break;
	case 'B':
		listType = BFS;
		animated = false;
		print = false;
		indexHighlight = -numColors;
		break;
	case '\n':
	case '\r':
		print = false;
		animated = !animated;
		indexHighlight = -numColors;
		traversalList.clear();
		switch(listType)
		{
		case BFS:


			breadthFirst(cursor, traversalList);
			break;
		case DFS:
			depthFirst(cursor, traversalList);
			break;
		}
	default:
		if(input >= 'a' && input <= 'z')
		{
			int neighbor = input -'a';
			if(neighbor < cursor->edges.size())
			{
				cursor->selected = false;
				cursor = cursor->edges.get(neighbor).to;
				cursor->selected = true;
				cursor->markAdj();
			}
		}
	}
	glutPostRedisplay();
}

/** @param a_width/a_height new dimensions of the resized window */
void reshape(int a_width, int a_height)
{
	// have the graphics context calculate the changes...
	g_screen.reshape(a_width, a_height);
	glutPostRedisplay();
}

/** @param x/y the coordinate of where the mouse is */
void passiveMotion(int x, int y)
{
}

/** @param x/y the coordinate of where the mouse is being dragged */
void draggedMotion(int x, int y)
{
	if(g_selected)
	{
		V2DF click = g_screen.convertPixelsToCartesian(V2DF(x,y));
		g_selected->set(click);
	}
}

/** 
* @param button (GLUT_LEFT_BUTTON || GLUT_MIDDLE_BUTTON || GLUT_RIGHT_BUTTON)
* @param state (GLUT_DOWN || GLUT_UP)
* @param x/y the coordinate of where the mouse is being dragged
*/
void mouse(int button, int state, int x, int y)
{
	switch(button)
	{
	case GLUT_LEFT_BUTTON:
		switch(state)
		{
		case GLUT_DOWN:
			{
				V2DF click = g_screen.convertPixelsToCartesian(V2DF(x,y));
				V2DF * closest = &nodeList.get(0).pos;
				GraphNode * select = &nodeList.get(0);
				float dist = closest->distance(click);
				float minDist = dist;
				for(int i = 0; i < nodeList.size(); ++i)
				{
					dist = nodeList.get(i).pos.distance(click);
					if(dist < minDist)
					{
						minDist = dist;
						closest = &nodeList.get(i).pos;
						select = &nodeList.get(i);
					}
				}
				if(minDist < RANGE)
				{
					dijkstra(&nodeList,select,out);
				}
			}
			break;
		case GLUT_UP:
			g_selected = 0;
			break;
		}
		break;
	case GLUT_RIGHT_BUTTON:
		switch(state)
		{
		case GLUT_DOWN:
			{
				V2DF click = g_screen.convertPixelsToCartesian(V2DF(x,y));
				V2DF * closest = &nodeList.get(0).pos;
				GraphNode * select = &nodeList.get(0);
				float dist = closest->distance(click);
				float minDist = dist;
				for(int i = 0; i < nodeList.size(); ++i)
				{
					dist = nodeList.get(i).pos.distance(click);
					if(dist < minDist)
					{
						minDist = dist;
						closest = &nodeList.get(i).pos;
						select = &nodeList.get(i);
					}
				}
				if(minDist < RANGE)
				{
					getPath(&out,select, nodes);
				}
			}
			break;
		case GLUT_UP:
			g_selected = 0;
			break;
		}
		break;
	}
	glutPostRedisplay();
}

/** @return true if the game changed state and should redraw */
bool update(int a_ms)
{
	g_body.update();
	return true;
}

/** call this function every value seconds*/
void updateTimerFunction(int value)
{
	/** keeps track of when the update method was called last */
	static clock_t g_lastTime = 0;
	// what time is it now?
	clock_t now = clock();
	// how much time has passed since update was called?
	clock_t passed = now-g_lastTime;
	// if update based on time
	if(update(passed))
	{
		// ask to refresh the display
		glutPostRedisplay();
	}
	// remember this last time that update started.
	g_lastTime = now;
	now = clock();
	// calc how long it took update to process, and factor that into the next wait
	passed = now - g_lastTime;
	int waitToCall = value-passed;
	if(waitToCall < 0)waitToCall = 0;
	// queue update again, factoring in the time update just took
	glutTimerFunc(waitToCall, updateTimerFunction, value);
}

void display()
{
	glClear(GL_COLOR_BUFFER_BIT);	//Clear the screen
	int red = 0x0000ff, green = 0x00ff00, blue = 0xff0000, gray = 0x888888;
	V2DF a(0,0), b(1,1);
	glColor3ubv((GLubyte*)&gray);
	g_screen.glDraw(0.5);			// draw the cartisian plane in white
	glColor3ubv((GLubyte*)&red);
	g_body.draw();
	if(g_selected)
	{
		glDrawCircle(*g_selected, .1);
	}
	if(animated)
	{
		for(int i = 0; i < traversalList.size(); ++i)
		{
			//setCursor(traversalList.get(i)->pos);
			if(i >= indexHighlight && i < indexHighlight+numColors)
			{
				setColor(colors[i-indexHighlight]);
				glDrawCircle(traversalList.get(i)->pos,rad);
				
				if((i+1)<traversalList.size())
				{
					traversalList.get(i)->pos.glDrawTo(traversalList.get(i+1)->pos);
				}
				
				glFlush();
				glutSwapBuffers();
			}
		}
		//Sleep(50);
		indexHighlight++;
		if(indexHighlight > traversalList.size()+numColors)
		{
			indexHighlight = -numColors;
		}
		//Sleep(50);
	}
	else if(print)
	{
		printDijkstra();
	}
	else
	{
		int select;
		for(int i = 0; i < nodeList.size(); i++)
		{
			if(nodeList.get(i).selected)
			{
				select = i;
			}
			else
			{
				glColor3ubv((GLubyte*)&red);
				nodeList.get(i).draw();
			}
			
		}
			glColor3ubv((GLubyte*)&green);
			glDrawCircle((nodeList.get(select).pos.x)+.2,(nodeList.get(select).pos.y)+.2,rad);
			nodeList.get(select).draw();
	}
	//printDjikstraData(&nodeList,&out);
	glFlush();						// print everything to the (off?) screen buffer
	glutSwapBuffers();				// swap the draw buffers
}

void init()
{
	// setup the screen using the graphics context
	g_screen.glSetupScreen();
	// define the drawing mode
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	//Create our window
	glutCreateWindow("data structures");
	// setup the call-back functions
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);
	glutReshapeFunc(reshape);
	glutPassiveMotionFunc(passiveMotion);
	glutMotionFunc(draggedMotion);
	glutMouseFunc(mouse);

	g_body.init();
}

int main(int argc, char ** argv)
{
	randomGraph(nodeList, 150, g_screen.getMaxX(), g_screen.getMaxY(), 5);
	breadthFirst(nodeList.getRawList(), traversalList);
	cursor = &nodeList.get(0);
	cursor->selected = true;
	cursor->markAdj();
	dijkstra(&nodeList,&nodeList.get(0),out);
	getPath(&out,&nodeList.get(0), nodes);

	// initialize GLUT
	glutInit(&argc, argv);
	// initialize the application
	init();
	// start the update timer-thread game-loop
	glutTimerFunc(0, updateTimerFunction, 50);

	// abstracted game loop that we don't get to see!!!
	glutMainLoop();
	return 0;
}

int randColors()
{
	return 0;
}

void printDijkstra()
{
	setColor(0xff);
	for(int i = 0; i<nodeList.size();i++)
		nodeList.get(i).drawNodes();
	for( int i = 0; i<nodes.size();++i)
		glDrawCircle(nodes.get(i)->pos.x+.2,nodes.get(i)->pos.y+.2,rad);
	glFlush();
	glutSwapBuffers();
	Sleep(200);
	for( int i = 0; i<nodes.size()-1;++i)
	{
		
		nodes.get(i)->pos.glDrawTo(nodes.get(i+1)->pos);
		glFlush();
		glutSwapBuffers();
		Sleep(200);
	}
	
}